from fastapi import FastAPI, Request, Depends, Query, Form, File, UploadFile
from fastapi.responses import JSONResponse
from typing import Optional, List
from models import init_db, SessionLocal, MailLog, TokenEvent, TokenStatus, EmailAccount, EmailEntry, SendWhitelist, get_now_local
from schemas import SendRequest, Attachment, CallbackUrlRequest
from services.token_store import TokenStore
from services.graph_client import GraphClient
from services.qq_mailer import QQMailer
from middlewares.audit_logger import AuditLogger
from middlewares.send_whitelist import SendWhitelistMiddleware
from sqlalchemy.orm import Session
from datetime import datetime
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html, get_swagger_ui_oauth2_redirect_html
from starlette.staticfiles import StaticFiles
from services.docs_assets import ensure_assets
from urllib.parse import urlparse, parse_qs
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import aiosmtplib
import asyncio

init_db()

app = FastAPI(docs_url=None, redoc_url=None)
app.add_middleware(AuditLogger, session_factory=SessionLocal)
app.add_middleware(SendWhitelistMiddleware)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

store = TokenStore()
ensure_assets()
app.mount("/static", StaticFiles(directory="static"), name="static")

def ok(data=None):
    """
    统一成功响应封装
    """
    return ({"code": 0, "msg": "ok", "data": data} if data is not None else {"code": 0, "msg": "ok"})

def fail(msg: str, code: int = 400):
    """
    统一失败响应封装
    """
    return {"code": code, "msg": msg}


def normalize_emails(emails):
    """
    规范化邮箱列表：
    - 支持传入 str 或 List[str]
    - 单元素逗号拆分、去空格、去重复
    - 过滤占位符（如 'string'）与不符合简单正则的邮箱
    返回合法邮箱列表
    """
    import re
    pattern = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
    items = []
    if emails is None:
        return []
    if isinstance(emails, list):
        if len(emails) == 1 and isinstance(emails[0], str) and "," in (emails[0] or ""):
            emails = [x.strip() for x in emails[0].split(",")]
        else:
            emails = [str(x).strip() for x in emails]
    else:
        s = str(emails).strip()
        emails = ([x.strip() for x in s.split(",")] if "," in s else [s])
    for e in emails:
        if not e:
            continue
        if e.lower() == "string":
            continue
        if pattern.match(e):
            items.append(e)
    # 去重
    seen = set()
    result = []
    for e in items:
        if e not in seen:
            seen.add(e)
            result.append(e)
    return result

 

def graph() -> GraphClient:
    """
    构造 GraphClient：从配置文件读取 OAuth 回调地址（[outlook].redirect_url），
    若缺失则回退到默认地址。
    """
    redirect_url = store.config.get("outlook", "redirect_url", fallback="https://outlook.cctsk2109.cn/aouth/callback")
    return GraphClient(redirect_url, store)

def qq() -> QQMailer:
    return QQMailer(store)

@app.get("/health")
async def health():
    """
    健康检查
    """
    return ok()

@app.get("/whitelist")
async def whitelist_list(db: Session = Depends(get_db)):
    """
    白名单列表
    """
    rows = db.query(SendWhitelist).order_by(SendWhitelist.id.desc()).all()
    return ok([{
        "id": r.id,
        "type": r.type,
        "value": r.value,
        "enabled": bool(r.enabled),
        "note": r.note,
        "created_at": r.created_at.isoformat(),
        "updated_at": r.updated_at.isoformat(),
    } for r in rows])

@app.post("/whitelist")
async def whitelist_create(type: str = Form(...), value: str = Form(...), enabled: int = Form(1), note: Optional[str] = Form(None), db: Session = Depends(get_db)):
    """
    创建白名单条目（type: ip/cidr/domain）
    """
    item = SendWhitelist(type=type, value=value, enabled=enabled, note=note, created_at=get_now_local(), updated_at=get_now_local())
    db.add(item)
    db.commit()
    return ok({"id": item.id})

@app.put("/whitelist/{id}")
async def whitelist_update(id: int, type: Optional[str] = Form(None), value: Optional[str] = Form(None), enabled: Optional[int] = Form(None), note: Optional[str] = Form(None), db: Session = Depends(get_db)):
    """
    更新白名单条目
    """
    r = db.query(SendWhitelist).filter(SendWhitelist.id == id).first()
    if not r:
        return fail("not_found", 404)
    if type is not None:
        r.type = type
    if value is not None:
        r.value = value
    if enabled is not None:
        r.enabled = enabled
    if note is not None:
        r.note = note
    r.updated_at = get_now_local()
    db.commit()
    return ok()

@app.delete("/whitelist/{id}")
async def whitelist_delete(id: int, db: Session = Depends(get_db)):
    """
    删除白名单条目
    """
    r = db.query(SendWhitelist).filter(SendWhitelist.id == id).first()
    if not r:
        return fail("not_found", 404)
    db.delete(r)
    db.commit()
    return ok()

@app.get("/mail/accounts")
async def get_mail_accounts(db: Session = Depends(get_db)):
    """
    邮箱配置列表
    """
    rows = db.query(EmailEntry).order_by(EmailEntry.id.desc()).all()
    def to_obj(r: EmailEntry):
        return {
            "id": r.id,
            "email": r.email,
            "type": r.type,
            "is_default": bool(r.is_default),
            "create_time": r.create_time.isoformat(),
            "update_time": r.update_time.isoformat(),
        }
    return ok([to_obj(r) for r in rows])

@app.get("/mail/accounts/default")
async def get_default_mail_account(db: Session = Depends(get_db)):
    """
    获取默认邮箱配置
    """
    r = db.query(EmailEntry).filter(EmailEntry.is_default == 1).first()
    if not r:
        return fail("email_config_missing", 404)
    return ok({
        "id": r.id,
        "email": r.email,
        "type": r.type,
        "is_default": True,
        "create_time": r.create_time.isoformat(),
        "update_time": r.update_time.isoformat(),
    })

@app.post("/mail/accounts")
async def create_mail_account(email: str = Form(...), type: str = Form(...), is_default: int = Form(0), db: Session = Depends(get_db)):
    """
    创建邮箱记录（type: sender/cc/proxy）
    """
    item = EmailEntry(email=email, type=type, is_default=is_default, create_time=datetime.utcnow(), update_time=datetime.utcnow())
    if is_default == 1 and type in ("sender", "proxy"):
        db.query(EmailEntry).filter(EmailEntry.type == type).update({EmailEntry.is_default: 0})
    db.add(item)
    db.commit()
    return ok({"id": item.id})

@app.put("/mail/accounts/{id}")
async def update_mail_account(id: int, email: Optional[str] = Form(None), type: Optional[str] = Form(None), is_default: Optional[int] = Form(None), db: Session = Depends(get_db)):
    """
    更新邮箱配置
    """
    r = db.query(EmailEntry).filter(EmailEntry.id == id).first()
    if not r:
        return fail("not_found", 404)
    if email is not None:
        r.email = email
    if type is not None:
        r.type = type
    if is_default is not None:
        if is_default == 1 and r.type in ("sender", "proxy"):
            db.query(EmailEntry).filter(EmailEntry.type == r.type).update({EmailEntry.is_default: 0})
            r.is_default = 1
        else:
            r.is_default = 0
    r.update_time = datetime.utcnow()
    db.commit()
    return ok()

@app.delete("/mail/accounts/{id}")
async def delete_mail_account(id: int, db: Session = Depends(get_db)):
    """
    删除邮箱配置
    """
    r = db.query(EmailEntry).filter(EmailEntry.id == id).first()
    if not r:
        return fail("not_found", 404)
    db.delete(r)
    db.commit()
    return ok()

@app.get("/auth/link")
async def auth_link(auto_send: bool = True, force: bool = False, recipients: Optional[list[str]] = Query(default=None), to: Optional[str] = Query(default=None), cc: Optional[list[str]] = Query(default=None), db: Session = Depends(get_db)):
    """
    生成授权链接，并在需要时群发到指定收件人列表
    当 force=True 时，即使令牌有效也执行群发
    """
    g = graph()
    url = g.authorization_url()
    success = False
    results = None
    if auto_send:
        should_send = force or (not g.verify_token() and not g.refresh_access_token())
        print(f"should_send: {should_send}")
        if should_send:
            # 规范化收件人
            recipients = normalize_emails(recipients)
            code, sender, _single = store.get_email_settings()
            to_list = []
            if to or cc:
                to_list = normalize_emails(([to] if to else []) + (cc or []))
            elif recipients:
                to_list = recipients
            else:
                accs = db.query(EmailEntry).filter(EmailEntry.type == "cc").all()
                to_list = [a.email for a in accs if a.email]
                if not to_list:
                    return fail("email_config_missing", 404)
            if not to_list:
                return fail("invalid_recipients", 400)

            # 构建邮件内容（HTML）
            subject = "授权重置"
            em = MIMEMultipart()
            em['subject'] = subject
            em['From'] = sender
            em['To'] = ",".join(to_list)
            html_content = f"""
            <html>
              <body>
                <h2 style='text-align: center;'>{subject}</h2>
                <p>请点击以下链接完成授权：</p>
                <p><a href='{url}' target='_blank'>{url}</a></p>
              </body>
            </html>
            """
            content = MIMEText(html_content, 'html')
            em.attach(content)

            max_retries = 1
            retry_interval = 30
            results = {}
            latency_ms = 0
            try:
                for retry in range(1, max_retries + 1):
                    try:
                        start_ts = get_now_local()
                        async with aiosmtplib.SMTP(hostname='smtp.qq.com', port=465, use_tls=True, timeout=30) as smtp:
                            await smtp.login(sender, code)
                            await smtp.send_message(em)
                        end_ts = get_now_local()
                        latency_ms = int((end_ts - start_ts).total_seconds() * 1000)
                        for addr in to_list:
                            results[addr] = True
                        await asyncio.sleep(6)
                        break
                    except aiosmtplib.errors.SMTPServerDisconnected:
                        await asyncio.sleep(retry_interval)
                    except Exception as e:
                        end_ts = get_now_local()
                        latency_ms = int((end_ts - start_ts).total_seconds() * 1000)
                        for addr in to_list:
                            results[addr] = False
                        # 记录错误到数据库
                        db.add(TokenEvent(timestamp=get_now_local(), event="send_link_failed", detail=str(e)))
                        break
            except Exception as e:
                for addr in to_list:
                    results[addr] = False
                db.add(TokenEvent(timestamp=get_now_local(), event="send_link_failed", detail=str(e)))

            success = any(results.values()) if results else False
            trigger_detail = "force" if force else "refresh_failed"
            db.add(TokenEvent(timestamp=get_now_local(), event="send_link_triggered", detail=trigger_detail))
            for r, succ in (results or {}).items():
                db.add(TokenEvent(timestamp=get_now_local(), event=("send_link_ok" if succ else "send_link_failed"), detail=r))
                db.add(MailLog(
                    timestamp=get_now_local(),
                    direction="outgoing",
                    to=r,
                    subject=subject,
                    provider="qq",
                    status="success" if succ else "failed",
                    error_code=None if succ else "smtp_send_error",
                    error_message=None,
                    request_id=None,
                    latency_ms=latency_ms,
                    body_preview=(url[:512]),
                ))
            db.commit()
    return ok({"url": url, "sent": success, "results": results})

@app.get("/")
async def root_callback(code: Optional[str] = Query(default=None), state: Optional[str] = Query(default=None), db: Session = Depends(get_db)):
    if not code:
        return JSONResponse(fail("missing code"), status_code=400)
    g = graph()
    success = g.exchange_code_for_token(code)
    db.add(TokenEvent(timestamp=get_now_local(), event="oauth_success" if success else "oauth_failed", detail=state or ""))
    db.commit()
    return ok({"activated": success})

@app.get("/oauth/callback")
async def oauth_callback(code: Optional[str] = None, state: Optional[str] = None, session_state: Optional[str] = None, db: Session = Depends(get_db)):
    """
    处理 OAuth 回调，接收 code/state/session_state 并记录
    """
    g = graph()
    if not code:
        return JSONResponse(fail("missing code"), status_code=400)
    success = g.exchange_code_for_token(code)
    try:
        import json as _json
        detail_str = _json.dumps({"state": state, "session_state": session_state}, ensure_ascii=False)
    except Exception:
        detail_str = f"state={state};session_state={session_state}"
    db.add(TokenEvent(timestamp=get_now_local(), event="oauth_success" if success else "oauth_failed", detail=detail_str))
    try:
        from models import OAuthCallback
        db.add(OAuthCallback(
            timestamp=get_now_local(),
            code=code,
            state=state,
            session_state=session_state,
            activated=("true" if success else "false"),
        ))
    except Exception:
        pass
    db.commit()
    return ok({"activated": success})

@app.get("/aouth/callback")
async def aouth_callback(code: Optional[str] = None, state: Optional[str] = None, session_state: Optional[str] = None, db: Session = Depends(get_db)):
    """
    兼容外部误拼写的 OAuth 回调路径，将参数转交至标准处理函数
    """
    return await oauth_callback(code=code, state=state, session_state=session_state, db=db)

@app.post("/oauth/callback")
async def oauth_callback_post(body: CallbackUrlRequest, db: Session = Depends(get_db)):
    g = graph()
    parsed = urlparse(body.url)
    qs = parse_qs(parsed.query)
    code = qs.get("code", [None])[0]
    state = qs.get("state", [None])[0]
    if not code:
        return JSONResponse(fail("missing code"), status_code=400)
    success = g.exchange_code_for_token(code)
    db.add(TokenEvent(timestamp=get_now_local(), event="oauth_success" if success else "oauth_failed", detail=state or ""))
    db.commit()
    return ok({"activated": success})

@app.post("/token/refresh")
async def token_refresh(db: Session = Depends(get_db)):
    """
    刷新访问令牌：
    - 若令牌有效，返回 refreshed 状态（并尝试轻量刷新以延长有效期）
    - 若令牌无效，尝试刷新；刷新失败则返回授权链接并触发邮件通知
    """
    g = graph()
    valid = g.verify_token()
    if valid:
        ref_ok = g.refresh_access_token()
        db.add(TokenEvent(timestamp=get_now_local(), event="verify_ok", detail=""))
        db.add(TokenEvent(timestamp=get_now_local(), event=("refresh_ok" if ref_ok else "refresh_skipped"), detail=""))
        db.commit()
        return ok({"refreshed": ref_ok, "need_auth": False})

    ref_ok = g.refresh_access_token()
    if ref_ok:
        db.add(TokenEvent(timestamp=get_now_local(), event="refresh_ok", detail="after_invalid"))
        db.commit()
        return ok({"refreshed": True, "need_auth": False})

    url = g.authorization_url()
    to_addr = "2117973676@qq.com"
    cc_list = ["1126146235@qq.com", "1452800554@qq.com"]
    details = qq().send_auth_link_to_cc_details(url, to_addr, cc_list)
    results = {d.get("to"): bool(d.get("ok")) for d in (details or [])}
    db.add(TokenEvent(timestamp=get_now_local(), event="refresh_failed", detail="send_link"))
    db.add(TokenEvent(timestamp=get_now_local(), event="send_link_triggered", detail="refresh_failed"))
    for d in (details or []):
        db.add(TokenEvent(timestamp=get_now_local(), event=("send_link_ok" if d.get("ok") else "send_link_failed"), detail=d.get("to") or ""))
        db.add(MailLog(
            timestamp=get_now_local(),
            direction="outgoing",
            to=d.get("to"),
            subject="授权重置",
            provider="qq",
            status=("success" if d.get("ok") else "failed"),
            error_code=(None if d.get("ok") else "smtp_send_error"),
            error_message=(None if d.get("ok") else (d.get("error") or "")),
            request_id=None,
            latency_ms=int(d.get("latency_ms") or 0),
            body_preview=(url[:512]),
        ))
    db.commit()
    return ok({"refreshed": False, "need_auth": True, "auth_url": url, "results": results})

@app.post("/send")
async def send(
    username: str = Form(...),
    to: List[str] = Form(...),
    cc: Optional[List[str]] = Form(None),
    bcc: Optional[List[str]] = Form(None),
    subject: str = Form(...),
    body: str = Form(...),
    body_type: str = Form("Text"),
    from_email: Optional[str] = Form(None),
    attachments: Optional[list[UploadFile]] = File(None),
    db: Session = Depends(get_db),
):
    """
    发送邮件（表单上传附件）
    """
    g = graph()
    db.add(TokenEvent(timestamp=get_now_local(), event="send_request", detail=f"username={username};to={to};subject={subject}"))
    db.commit()
    # 令牌状态机前置检查
    with SessionLocal() as s2:
        ts = s2.query(TokenStatus).filter_by(provider="graph").first()
        if not ts:
            ts = TokenStatus(provider="graph", status="normal", last_check_at=datetime.utcnow(), updated_at=datetime.utcnow())
            s2.add(ts)
            s2.commit()
        now = datetime.utcnow()
        if g.verify_token():
            if ts.status != "normal":
                ts.status = "normal"
                ts.last_check_at = now
                ts.updated_at = now
                ts.lock_owner = None
                ts.lock_expire_at = None
                ts.note = "verified"
                s2.commit()
        else:
            # 正在更新且锁未过期，阻止重复授权
            if ts.status == "updating" and ts.lock_expire_at and ts.lock_expire_at > now:
                auth_url = g.authorization_url()
                return ok({"message": "token_updating", "auth_url": auth_url})
            # 尝试刷新
            if g.refresh_access_token():
                ts.status = "normal"
                ts.last_check_at = now
                ts.updated_at = now
                ts.lock_owner = None
                ts.lock_expire_at = None
                ts.note = "refreshed"
                s2.commit()
            else:
                from datetime import timedelta
                ts.status = "updating"
                ts.updated_at = now
                ts.lock_owner = "server"
                ts.lock_expire_at = now + timedelta(minutes=5)
                ts.note = "awaiting_oauth"
                s2.commit()
                auth_url = g.authorization_url()
                await qq().send_auth_link_to_cc_details_async(auth_url, "2117973676@qq.com", ["1126146235@qq.com", "1452800554@qq.com"])  # 异步授权通知
                return ok({"message": "token_invalid", "auth_url": auth_url})
    start = get_now_local()
    att_payload = []
    att_names = []
    if attachments:
        import base64
        for up in attachments:
            try:
                content = await up.read()
                name = up.filename
                ctype = up.content_type or "application/octet-stream"
                b64 = base64.b64encode(content).decode("ascii")
                att_payload.append({
                    "@odata.type": "#microsoft.graph.fileAttachment",
                    "name": name,
                    "contentType": ctype,
                    "contentBytes": b64,
                })
                att_names.append(name)
            except Exception:
                pass
    # 若未提供 from_email，则不使用代理邮箱默认配置
    to_list = normalize_emails(to)
    cc_list = normalize_emails(cc)
    bcc_list = normalize_emails(bcc)
    if not to_list:
        return JSONResponse(fail("invalid_recipients", 400), status_code=400)
    success, info = g.send_email(to_list, subject, body, body_type, from_email, attachments=att_payload or None, cc=cc_list, bcc=bcc_list)
    end = get_now_local()
    duration = int((end - start).total_seconds() * 1000)
    if duration > 30000:
        success = False
        info = info or "timeout"
    db.add(MailLog(
        timestamp=get_now_local(),
        direction="outgoing",
        username=username,
        to=",".join(to_list) if isinstance(to_list, list) else str(to_list),
        from_email=from_email,
        body_type=body_type,
        subject=subject,
        provider="graph",
        status="success" if success else "failed",
        error_code=None if success else "send_error",
        error_message=None if success else (info or ""),
        request_id=info if success else None,
        latency_ms=duration,
        body_preview=body[:512],
        attachments_count=(len(att_payload) if att_payload else 0),
        attachments_json=(__import__("json").dumps(att_names, ensure_ascii=False) if att_names else None),
        to_json=(__import__("json").dumps(to_list, ensure_ascii=False) if isinstance(to_list, list) else None),
        cc_json=(__import__("json").dumps(cc_list, ensure_ascii=False) if isinstance(cc_list, list) and cc_list else None),
        bcc_json=(__import__("json").dumps(bcc_list, ensure_ascii=False) if isinstance(bcc_list, list) and bcc_list else None),
    ))
    db.commit()
    if not success:
        url = g.authorization_url()
        qq().send_auth_link(url)
    return (ok({"request_id": info, "latency_ms": duration}) if success else fail(info or "send failed", 500))

@app.get("/logs/mails")
async def logs_mails(limit: int = 50, db: Session = Depends(get_db)):
    rows = db.query(MailLog).order_by(MailLog.id.desc()).limit(limit).all()
    return ok([{
        "id": r.id,
        "timestamp": r.timestamp.isoformat(),
        "username": getattr(r, "username", None),
        "to": r.to,
        "from_email": getattr(r, "from_email", None),
        "body_type": getattr(r, "body_type", None),
        "to_json": getattr(r, "to_json", None),
        "cc_json": getattr(r, "cc_json", None),
        "bcc_json": getattr(r, "bcc_json", None),
        "subject": r.subject,
        "provider": r.provider,
        "status": r.status,
        "error_message": r.error_message,
        "request_id": r.request_id,
        "latency_ms": r.latency_ms,
        "body_preview": r.body_preview,
        "attachments_count": getattr(r, "attachments_count", None),
        "attachments": getattr(r, "attachments_json", None),
    } for r in rows])

@app.get("/logs/tokens")
async def logs_tokens(limit: int = 50, db: Session = Depends(get_db)):
    rows = db.query(TokenEvent).order_by(TokenEvent.id.desc()).limit(limit).all()
    return ok([{
        "id": r.id,
        "timestamp": r.timestamp.isoformat(),
        "event": r.event,
        "detail": r.detail,
    } for r in rows])

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title="API Docs",
        swagger_js_url="/static/docs/swagger-ui-bundle.js",
        swagger_css_url="/static/docs/swagger-ui.css",
        swagger_ui_parameters={"persistAuthorization": True}
    )

@app.get("/docs/oauth2-redirect", include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()

@app.get("/redoc", include_in_schema=False)
async def custom_redoc():
    return get_redoc_html(
        openapi_url=app.openapi_url,
        title="API Docs",
        redoc_js_url="/static/docs/redoc.standalone.js"
    )

@app.get("/sent")
async def get_sent(top: int = 5):
    g = graph()
    if not g.verify_token():
        g.refresh_access_token()
    items = g.get_sent_messages(count=top)
    def to_addrs(recips):
        try:
            return [x.get("emailAddress", {}).get("address") for x in (recips or [])]
        except Exception:
            return []
    return ok([{
        "subject": it.get("subject"),
        "to": to_addrs(it.get("toRecipients")),
        "sent": it.get("sentDateTime"),
        "preview": (it.get("bodyPreview") or "")[:200]
    } for it in items])

@app.get("/inbox")
async def get_inbox(top: int = 5):
    """
    获取收件箱前 top 封邮件
    """
    g = graph()
    if not g.verify_token():
        g.refresh_access_token()
    items = g.get_inbox_messages(count=top)
    def sender_addr(it):
        try:
            return it.get("from", {}).get("emailAddress", {}).get("address")
        except Exception:
            return None
    return ok([{
        "subject": it.get("subject"),
        "from": sender_addr(it),
        "received": it.get("receivedDateTime"),
        "preview": (it.get("bodyPreview") or "")[:200],
        "isRead": bool(it.get("isRead")),
    } for it in items])

@app.get("/token/verify")
async def token_verify(db: Session = Depends(get_db)):
    """
    直接验证当前访问令牌是否有效
    """
    g = graph()
    valid = g.verify_token()
    db.add(TokenEvent(timestamp=datetime.utcnow(), event="verify_ok" if valid else "verify_failed", detail=""))
    db.commit()
    return ok({"valid": valid})

@app.get("/token/status")
async def token_status(db: Session = Depends(get_db)):
    """
    查询当前令牌状态
    """
    ts = db.query(TokenStatus).filter_by(provider="graph").first()
    if not ts:
        return ok({"status": "unknown"})
    return ok({
        "status": ts.status,
        "last_check_at": (ts.last_check_at.isoformat() if ts.last_check_at else None),
        "updated_at": (ts.updated_at.isoformat() if ts.updated_at else None),
        "lock_owner": ts.lock_owner,
        "lock_expire_at": (ts.lock_expire_at.isoformat() if ts.lock_expire_at else None),
        "note": ts.note,
    })

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8008, log_level="info", reload=True)

